package cn.admin.remoting.impl;

import cn.admin.remoting.ConnectCancelledException;
import cn.admin.remoting.Connection;
import cn.admin.remoting.IOEventListener;
import cn.admin.remoting.RequestProcessor;
import cn.admin.remoting.locale.LogResources;
import cn.admin.remoting.util.ConnectionUrl;
import cn.admin.remoting.util.LoggerInit;
import org.apache.mina.common.*;
import org.apache.mina.common.support.AbstractIoFilterChain;
import org.apache.mina.transport.socket.nio.SocketAcceptor;

import java.util.List;

public class DefaultIoHandler extends IoHandlerAdapter {

    private final DefaultConnectionFactory connectionFactory;

    DefaultIoHandler(DefaultConnectionFactory factory) {
        this.connectionFactory = factory;
    }

    private DefaultConnection createConnection(IoSession session) {
        DefaultConnection conn = DefaultConnection.findBySession(session);
        if (conn == null) {
            conn = new DefaultConnection(session,this.connectionFactory);
        }

        if (session.getService() instanceof SocketAcceptor) {
            new DefaultClient(session,null);
        }

        return conn;
    }

    @Override
    public void sessionOpened(IoSession session) throws Exception {
        if (session.getIdleTime(IdleStatus.READER_IDLE) <= 0) {
            session.setIdleTime(IdleStatus.READER_IDLE,30);
        }
        Connection connection = this.createConnection(session);
        this.callConnectionOpenedListeners(connection,connection.getConnectionEvtListeners());
        this.callConnectionOpenedListeners(connection,this.connectionFactory.globalListeners);
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        Connection connection = DefaultConnection.findBySession(session);
        this.callConnectionClosedListeners(connection,connection.getConnectionEvtListeners());
        this.callConnectionClosedListeners(connection,this.connectionFactory.globalListeners);
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
        Connection connection = DefaultConnection.findBySession(session);
        this.callConnectionIdleListeners(connection,connection.getConnectionEvtListeners());
        this.callConnectionIdleListeners(connection,this.connectionFactory.globalListeners);
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        DefaultConnection connection = DefaultConnection.findBySession(session);
        connection.getMsgReceivedListener().messageReceived(connection,message);
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
        Connection connection = DefaultConnection.findBySession(session);
        if (cause instanceof WriteTimeoutException) {
            LoggerInit.LOGGER.error(LogResources.getLog(LogResources.CONNECTION_WRITE_TIMEOUT, connection), cause);
        } else {
            LoggerInit.LOGGER.error(LogResources.getLog(LogResources.CONNECTION_EXCEPTION, connection, cause), cause);
        }
    }

    private void callConnectionIdleListeners(Connection connection, List<IOEventListener> listeners) {
        for (IOEventListener listener : listeners) {
            if (listener instanceof IOEventListener.ConnectionIdleListener) {
                try {
                    ((IOEventListener.ConnectionIdleListener)listener).connectionIdle(connection);
                } catch (Exception e) {
                    LoggerInit.LOGGER.warn(e.getMessage(),e);
                }
            }
        }
    }

    private void callConnectionClosedListeners(Connection connection, List<IOEventListener> listeners) {
        for (IOEventListener listener : listeners) {
            if (listener instanceof IOEventListener.ConnectionLifecycleListener) {
                try {
                    ((IOEventListener.ConnectionLifecycleListener) listener).connectionClosed(connection);
                } catch (Exception e) {
                    LoggerInit.LOGGER.warn(e.getMessage(),e);
                }
            }
        }
    }

    private void callConnectionOpenedListeners(Connection connection, List<IOEventListener> listeners) {
        for (IOEventListener listener : listeners) {
            if (listener instanceof IOEventListener.ConnectionLifecycleListener) {
                try {
                    ((IOEventListener.ConnectionLifecycleListener) listener).connectionOpened(connection);
                } catch (Exception e) {
                    LoggerInit.LOGGER.warn(e.getMessage(),e);
                }
            }
        }
    }

    static class ClientIoHandler extends DefaultIoHandler {

        private final String clientKey;
        private final List<IOEventListener> ioListeners;

        ClientIoHandler(ClientConnectionFactory ccf,String clientKey,
                        List<IOEventListener> ioListeners) {
            super(ccf);
            this.clientKey = clientKey;
            this.ioListeners = ioListeners;
        }

        @Override
        public void sessionOpened(IoSession session) throws Exception {
            ConnectFuture cf = (ConnectFuture) session.getAttribute(AbstractIoFilterChain.CONNECT_FUTURE);

            try {
                cf.getSession();
            } catch (RuntimeIOException e) {
                Throwable cause = e.getCause();
                if (cause != null && cause.getClass() == ConnectCancelledException.class) {
                    session.setAttribute("closeConnReason","ConnectionRequestCancelled");
                    session.close();
                    throw (ConnectCancelledException)cause;
                }
            }

            String targetUrl = ClientKeyUtil.targetUrl(this.clientKey);
            int idleTimeout = ConnectionUrl.getIdleTimeout(targetUrl);

            if (0 < idleTimeout) {
                session.setIdleTime(IdleStatus.READER_IDLE,idleTimeout);
            }

            DefaultConnection connection = super.createConnection(session);
            while (true) {
                for (IOEventListener listener : ioListeners) {
                    if (listener instanceof IOEventListener.ConnectionMsgReceivedListener) {
                        IOEventListener.ConnectionMsgReceivedListener msgListener =
                                (IOEventListener.ConnectionMsgReceivedListener) listener;
                        for (RequestProcessor<?> p : msgListener.getProcessors().values()) {
                            connection.getMsgReceivedListener().registerProcessor(p);
                        }
                    } else {
                        connection.addConnectionEvtListener(listener);
                    }
                }

                super.sessionOpened(session);
                return;
            }
        }
    }
}
